import { BaseDatabase } from '../common/BaseDatabase';
import { relationalStore, ValueType } from '@kit.ArkData';
import { Column, ColumnType, ReferencesColumn } from '../schema/Column';
import { Table } from '../schema/Table';
import { DatabaseSource, PickModelType, RdbPredicateScope, SqlReturnType } from '../common/Types';
import { Internal } from '../common/Internal';
import { Check } from '../common/Check';
import { RdbPredicateWrapper } from '../common/RdbPredicateWrapper';
import { QuerySQL } from '../common/SQL';

export class SQLiteDatabase extends BaseDatabase {
  private async modelToValueBucket<M>(
    targetTable: Table<M>,
    value: M,
    columns: readonly ColumnType[] = targetTable[Internal.TableOptions].columns): Promise<relationalStore.ValuesBucket> {
    const vb: relationalStore.ValuesBucket = {}
    for (const column of columns) {
      const item = value[column.key]
      if (column instanceof ReferencesColumn) {
        Check.checkTableHasAtMostOneIdColumn(column.referencesTable)
        const useOptions = column.referencesTable[Internal.TableOptions]
        const idColumn = useOptions.idColumns[0]
        if (item) {
          vb[column.fieldName] = item?.[idColumn.key]
        } else {
          vb[column.fieldName] = item
        }
        continue
      }
      if (column instanceof Column) {
        if (item && column.typeConverters) {
          vb[column.fieldName] = column.typeConverters.save(item)
        } else {
          vb[column.fieldName] = item
        }
      }
    }
    return vb
  }

  private async valueBucketToModel<M>(
    targetTable: Table<M>,
    value: relationalStore.ValuesBucket,
    columns: readonly ColumnType[] = targetTable[Internal.TableOptions].columns): Promise<M> {
    const vb = {}
    for (const column of columns) {
      const item = value[column.fieldName]
      if (column instanceof ReferencesColumn) {
        Check.checkTableHasAtMostOneIdColumn(column.referencesTable)
        const useOptions = column.referencesTable[Internal.TableOptions]
        const idColumn = useOptions.idColumns[0]
        if (item) {
          const resultSet =
            await this.rdbStore.query(RdbPredicateWrapper.select(column.referencesTable).equalTo(idColumn, item)
              .getRdbPredicates())
          const row = resultSet.goToNextRow() ? resultSet.getRow() : null
          vb[column.key] = row ? await this.valueBucketToModel(column.referencesTable, row) : null
        } else {
          vb[column.key] = undefined
        }
        continue
      }
      if (column instanceof Column) {
        vb[column.key] = item && column.typeConverters ? column.typeConverters.restore(item) : item
      }
    }
    return vb as M
  }

  /**
   * 将一行数据插入到目标表中
   *
   * @param table 目标表
   * @param value 要插入的数据
   * @param conflict 遇到冲突的解决模式
   * @returns 如果操作成功，返回行 ID，否则返回 -1
   */
  async insert<M>(
    table: Table<M>,
    value: M,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    const vb = await this.modelToValueBucket(table, value)
    const tableOptions = table[Internal.TableOptions]
    if (tableOptions.idColumns.length === 1) {
      const idColumn = tableOptions.idColumns[0]
      const isRowIdAlias = idColumn && idColumn.isAutoincrement && idColumn.dataType === 'INTEGER'
      // 对于 ID 列是自增的情况下，如果 ID 的值不是数值，则移除该列以确保使用自增值
      if (isRowIdAlias && !(vb[idColumn.fieldName] instanceof Number)) {
        delete vb[idColumn.fieldName]
      }
      const rowId = await this.rdbStore.insert(table.tableName, vb, conflict)
      if (isRowIdAlias) {
        value[idColumn.key] = rowId
      }
      return rowId
    } else {
      return this.rdbStore.insert(table.tableName, vb, conflict)
    }
  }

  /**
   * 插入一组数据到目标表中
   *
   * @param table 目标表
   * @param value 要插入的数据
   * @returns 如果操作成功，返回插入的数据个数，否则返回 -1
   */
  async batchInsert<M>(table: Table<M>, value: M[]): Promise<number> {
    const vbs = await Promise.all(value.map(item => this.modelToValueBucket(table, item)))
    return this.rdbStore.batchInsert(table.tableName, vbs)
  }

  /**
   * 根据实体中的主键来更新数据库中的数据
   *
   * @param table 目标表
   * @param value 要更新的实体
   * @returns 返回受影响的行数
   * @throws 不存在主键时将终止
   */
  async update<M>(
    table: Table<M>,
    value: M,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    Check.checkTableHasIdColumn(table)
    const tableOptions = table[Internal.TableOptions]
    const queryPredicate = RdbPredicateWrapper.select(table)
    for (const idColumnsKey of tableOptions.idColumns) {
      queryPredicate.equalTo(idColumnsKey, value[idColumnsKey.key])
    }
    return this.rdbStore.update(await this.modelToValueBucket(table, value), queryPredicate.getRdbPredicates(),
      conflict)
  }

  /**
   *  根据条件更新数据库中的数据
   *
   * @param table 目标表
   * @param predicate 条件
   * @param value 要更新的数据
   * @param conflict 遇到冲突的解决模式
   * @returns 返回受影响的行数
   */
  async updateIf<M>(
    table: Table<M>,
    predicate: RdbPredicateWrapper<M>,
    value: Partial<M>,
    conflict: relationalStore.ConflictResolution = relationalStore.ConflictResolution.ON_CONFLICT_NONE): Promise<number> {
    const rdbPredicate = predicate.getRdbPredicates()
    const columns = Object.keys(value)
      .map(key => table[Internal.TableOptions].columns.find(item => item.key === key))
      .filter(Boolean)
    return this.rdbStore.update(await this.modelToValueBucket(table, value, columns), rdbPredicate, conflict)
  }

  /**
   * 根据实体中的主键来删除数据库中的数据
   *
   * @param table 目标表
   * @param value 要删除的实体
   * @returns 返回受影响的行数
   * @throws 不存在主键时将终止
   */
  async delete<M>(table: Table<M>, value: M): Promise<number> {
    Check.checkTableHasIdColumn(table)
    const tableOptions = table[Internal.TableOptions]
    const queryPredicate = RdbPredicateWrapper.select(table)
    for (const idColumnsKey of tableOptions.idColumns) {
      queryPredicate.equalTo(idColumnsKey, value[idColumnsKey.key])
    }
    return this.rdbStore.delete(queryPredicate.getRdbPredicates())
  }

  /**
   * 根据条件删除数据库中的数据
   *
   * @param table 目标表
   * @param predicates 条件
   * @returns 返回受影响的行数
   */
  async deleteIf<M>(table: Table<M>, predicates: RdbPredicateScope<M>): Promise<number> {
    return this.rdbStore.delete(predicates(RdbPredicateWrapper.select(table)).getRdbPredicates())
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param table 目标表
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以实体数组的形式返回查询到的数据
   */
  async query<T extends ColumnType[], M>(
    table: Table<M>,
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<PickModelType<M, T>[]> {
    const selectedColumns = columns.length === 0 ? undefined : columns
    const rdbPredicates = predicates(RdbPredicateWrapper.select(table)).getRdbPredicates()
    const resultSet = await this.rdbStore.query(rdbPredicates)
    const promises: Promise<M>[] = []
    while (resultSet.goToNextRow()) {
      promises.push(this.valueBucketToModel(table, resultSet.getRow(), selectedColumns))
    }
    return Promise.all(promises)
  }

  /**
   * 根据条件查询数据库中的数据
   *
   * @param table 目标表
   * @param predicates 条件
   * @param columns 指定查询的列，为空则查询全部列
   * @returns 以单个实体的形式返回查询到的数据
   */
  async queryOne<T extends ColumnType[], M>(
    table: Table<M>,
    predicates: RdbPredicateScope<M> = it => it,
    ...columns: T): Promise<PickModelType<M, T>> {
    const selectedColumns = columns.length === 0 ? undefined : columns
    const rdbPredicates = predicates(RdbPredicateWrapper.select(table).limitAs(1)).getRdbPredicates()
    const resultSet = await this.rdbStore.query(rdbPredicates)
    try {
      while (resultSet.goToNextRow()) {
        return this.valueBucketToModel(table, resultSet.getRow(), selectedColumns)
      }
    } finally {
      resultSet.close()
    }
  }

  /**
   * 根据 SQL 查询数据库中的数据
   *
   * @param sql SQL
   * @param bindArgs 绑定的参数
   * @returns 以 relationalStore.ValuesBucket 类型数组的形式返回查询到的数据
   */
  async querySql(sql: string, bindArgs?: Array<ValueType>): Promise<relationalStore.ValuesBucket[]>

  /**
   *  根据 SQL 查询数据库中的数据
   *
   * @param sql SQL
   * @returns 以包装 relationalStore.ValuesBucket 类型数组的形式返回查询到的数据
   */
  async querySql<T extends QuerySQL<any>>(sql: T): Promise<SqlReturnType<T>[]>

  async querySql<T extends QuerySQL<any>>(
    sql: string | T,
    bindArgs?: Array<ValueType>): Promise<relationalStore.ValuesBucket[] | SqlReturnType<T>[]> {
    // 实现逻辑
    const resultSet = await this.rdbStore.querySql(sql.toString(), bindArgs)
    const resultRows: relationalStore.ValuesBucket[] = []
    while (resultSet.goToNextRow()) {
      const row = resultSet.getRow()
      resultRows.push(row)
    }
    try {
      return resultRows
    } finally {
      resultSet.close()
    }
  }

  /**
   * 执行包含指定参数但不返回任何值的 SQL
   *
   * @param sql SQL
   * @returns 返回 void
   */
  async executeSql(sql: string): Promise<void> {
    return this.rdbStore.executeSql(sql)
  }

  /**
   * 执行包含指定参数的 SQL，并返回 ValueType 值
   *
   * @param sql SQL
   * @returns 返回 ValueType
   */
  async execute(sql: string): Promise<relationalStore.ValueType> {
    return this.rdbStore.execute(sql)
  }

  /**
   * 执行事务操作，失败自动回退
   *
   * @param scope 事务处理函数，接收当前 SQLiteDatabase 实例作为参数
   * @returns Promise
   */
  async launchTransaction<T>(scope: (it: this) => T): Promise<T> {
    const txId = await this.beginTrans()
    return new Promise((resolve, reject) => {
      try {
        const result = scope(this)
        this.commit(txId)
        resolve(result)
      } catch (e) {
        this.rollback(txId)
        reject(e)
      }
    })
  }

  /**
   * 开始事务
   *
   * @returns 返回事务 ID
   */
  async beginTrans(): Promise<number> {
    return this.rdbStore.beginTrans()
  }

  /**
   * 提交事务操作
   *
   * @param txId 事务 ID
   * @returns 返回 void
   */
  async commit(txId: number): Promise<void> {
    return this.rdbStore.commit(txId)
  }

  /**
   * 回滚事务操作
   *
   * @param txId 事务 ID
   * @returns 返回 void
   */
  async rollback(txId: number): Promise<void> {
    return this.rdbStore.rollback(txId)
  }

  /**
   * 用于创建 SQLiteDatabase 的实例
   *
   * @param database 数据源，支持 RdbStore、Database、Dao
   * @returns SQLiteDatabase
   */
  static form(database: DatabaseSource) {
    return new SQLiteDatabase(database)
  }
}